#include "my_gets_and_my_puts.h"
#include "check.h"

//数据库查询语句，返回查询个数(行数),传入传出参数存储查询的字符串(只查某一行的一个字段)
//参数为mysql连接，查找字段，查找表，查找条件
//select select_fileld from table_name where condition;
int select_field(MYSQL * mysql, char * serch_fileld, const char * table_name, const char * condition) {
    char select_buf[1024] = {0};
    //拼接查询语句
    sprintf(select_buf, "select %s from %s where %s;", serch_fileld, table_name, condition);
    int ret = mysql_query(mysql, select_buf);
    ERRNO_LOGGING(ret, -1, "mysql_query if failed");
    //取出查询结果表
    MYSQL_RES * table;
    memset(table, 0, sizeof(table));
    table = mysql_store_result(mysql);
    ERRNO_LOGGING(table, NULL, "mysql_store_result is failed");
    //计算查询行数
    int count = mysql_num_rows(table);
    //取出一行
    MYSQL_ROW row = mysql_fetch_row(table);
    //取出行中的第一个字段，深拷贝
    memset(serch_fileld, 0, strlen(serch_fileld));
    memcpy(serch_fileld, row[0], strlen(row[0]) + 1);
    mysql_free_result(table);
    return count;

}

//数据库添加操作(添加一行)，instert into ,参数为数据库连接，表名，需要添加的行
//返回值0表示插入成功，非0失败
//insert into table_name row;
int insert_into__row(MYSQL * mysql, const char * table_name, char * row) {
    char insert_into_buf[1024] = {0};
    //拼接插入语句
    sprintf(insert_into_buf, "insert into %s %s;", table_name, row);
    int ret = mysql_query(mysql, insert_into_buf);
    ERRNO_LOGGING(ret, -1, "mysql_query if failed");
    return ret;
}
//数据库字段修改操作，修改某个条件的字段的值,update table_name set result  where condition;
//参数为数据库连接，表名，修改的结果，条件
int update_fileld(MYSQL * mysql, const char * table_name,const char * result, const char * condition) {
    char update_buf[1024] = {0};
    //拼接数据更新语句
    sprintf(update_buf, "update %s set %s where %s;", table_name, result, condition);
    int ret = mysql_query(mysql, update_buf);
    ERRNO_LOGGING(ret, -1, "mysql_query if failed");
    return ret;
} 
//数据库删除行操作(物理删除，逻辑删除用update_fileld)
//delete from table_name where condition;
//参数为数据库连接，表名，条件
//返回0成功，非0失败
int delete_from_row(MYSQL * mysql, const char * table_name, const char * condition) {
    char delete_from_buf[1024] = {0};
    //拼接删除语句
    sprintf(delete_from_buf, "delete from %s where %s;", table_name, condition);
    int ret = mysql_query(mysql, delete_from_buf);
    ERRNO_LOGGING(ret, -1, "mysql_query if failed");
    return ret;
}
//一次性读取len个字符，返回值为len,为-1时代表客户端断开                                                                        
int recvn(int netfd,void * buf, int len) {
    char *p = (char*)buf;
    int cur = 0;
    while (cur < len) {
        int ret = recv(netfd, p + cur, len - cur, 0); 
        ERRNO_LOGGING(ret, -1, "recv failed");
        if (ret == 0) {
            WARNING_LOGGING(ret, 0, "recvn return -1");
            return -1;
        }
        cur += ret;
    }   
    return len;
}
//小火车装车，返回值检错
int padding_train_by_buf(Train_t * train, void * buf,  DataType_t type, int len) {
    train->data_type = type;
    train->data_len = len;
    memcpy(train->data, buf, len);

    return 0;
}
//接收小火车,返回值为-1时代表客户端断开
int recv_train(int fd, Train_t * dest) {
    //接收type和len
    int type_and_len_len = sizeof(dest->data_type) + sizeof(dest->data_len);
    int ret = recvn(fd, &dest->data_type,type_and_len_len);
    if (ret == -1) {
        WARNING_LOGGING(ret, 0, "recvn_train return -1");
        return -1;
    }
    printf("type = %d, len = %d",dest->data_type, dest->data_len);
    //接收data
    ret = recvn(fd, dest->data,dest->data_len);
    printf("data = %s\n",dest->data);
    if (ret == -1) {
        WARNING_LOGGING(ret, 0, "recvn_train return -1");
        return -1;
    }
    return 0;
}
//发送小火车,返回值为send的返回值，用于判断客户端是否断开
int send_train(int netfd, Train_t * train) {
    int len = sizeof(train->data_len) + sizeof(train->data_type) + train->data_len;
    int ret = send(netfd, train, len, MSG_NOSIGNAL);
    return ret;
} 
//大文件发送
int send_big_file(int netfd,int src_fd, off_t len, off_t offset) {
    char * buf = (char*)mmap(NULL, len + offset, PROT_READ | PROT_WRITE, MAP_SHARED, src_fd, 0);
    ERRNO_LOGGING(buf, MAP_FAILED, "mmap failed");
    int ret = send(netfd, buf + offset, len, MSG_NOSIGNAL);
    WARNING_LOGGING(ret, -1, "a client is die in getting");
    munmap(buf, len + offset);
    close(src_fd);
    return 0;
}
//小文件发送
int send_little_file(int netfd,int src_fd, off_t len) {
    off_t cur = 0;
    char buf[4096];
    while (cur < len) {
        memset(buf, 0, sizeof(buf));
        int ret = read(src_fd, buf, sizeof(buf));
        WARNING_LOGGING(ret, -1, "a client is die in getting");
        send(netfd, buf, ret, MSG_NOSIGNAL);
        cur += ret;
    }
    close(src_fd);
    return 0;
}
//大文件接收
int recv_and_gets_big_file(int dest_fd, int src_fd, off_t len,off_t offset) {
    ftruncate(dest_fd, len + offset);
    char * buf = (char*)mmap(NULL, len + offset, PROT_READ | PROT_WRITE, MAP_SHARED, dest_fd, 0);
    ERRNO_LOGGING(buf, MAP_FAILED, "mmap failed");
    int ret = recvn(src_fd, buf + offset, len);
    if (ret == -1) {
        INFO_LOGGING("recv_and_gets_big_file return -1");
        return -1;
    }
    munmap(buf, len + offset);
    close(dest_fd);
    return 0;

}
//小文件接收,src_fd是socket文件描述符,dest_fd是磁盘文件的文件描述符
int recv_and_gets_little_file(int dest_fd, int src_fd, off_t len) {
    off_t cur = 0;
    char buf[4096];
    while (cur < len) {
        memset(buf, 0, sizeof(buf));
        int ret = recv(src_fd, buf, sizeof(buf), 0);
        if (ret == 0) {
            INFO_LOGGING("recv_and_gets_little_file return -1");
            return -1;
        }
        write(dest_fd, buf, ret);
        cur += ret;
    }
    close(dest_fd);
    return 0;
}


//my_gets下载服务端is_exist,下载时服务器路径为src客户端路径为dest
//server_path是不带文件名的path
int my_gets(int netfd,MYSQL*mysql,const char*filename,const char*server_path,const char*client_path,const char*user_name,const char*file_table_name) {
    //判断文件是否存在，用slect_field函数,用文件的绝对路径去查找md5
    char serch_field[100] = {"md5"};
    char condition[100] = {0};
    sprintf(condition, "path = '%s/%s' and username = '%s'", server_path, filename, user_name);
    int is_exist = select_field(mysql, serch_field, file_table_name, condition);
    Train_t train;
    if (is_exist == 0){//文件不存在
                       //发送错误处理的小火车
        char errbuf[] = "gets: 没有这个文件";
        padding_train_by_buf(&train, errbuf, GETS_FAILED,sizeof(errbuf));
        int ret  = send_train(netfd, &train);
        WARNING_LOGGING(ret, -1, "client is die in getting\n");
        WARNING_LOGGING(1, 1, "gets: 服务器不存在这个文件\n");
        return 0;
    }
    //存在文件
    //将客户端路径发回客户端
    padding_train_by_buf(&train, (void*)client_path, COMMAND_GETS,strlen(client_path) + 1);
    int ret = send_train(netfd, &train);
    WARNING_LOGGING(ret, -1, "client is die in getting\n");
    //用路径打开文件
    int src_file_fd = open(server_path, O_RDWR);
    ERRNO_LOGGING(src_file_fd, -1, "open file failed");
    //用fstat查看文件大小
    struct stat state;
    fstat(src_file_fd, &state);
    off_t size = state.st_size;
    //记录一下偏移量
    off_t offset = 0;
    //接收客户端的小火车
    int sret = recv_train(netfd, &train);
    if (sret == -1) {
        INFO_LOGGING("a client is die");
        return -1;
    }
    if (train.data_type == SAME_NAME_FILE) {//是重名文件
                                            //接收客户端发送的md5值和客户端文件大小
        char client_md5_val[33] = {0};
        off_t client_file_size;
        //保存md5值
        memcpy(client_md5_val, train.data, train.data_len);
        //接收文件大小
        sret = recv_train(netfd, &train);
        if (sret == -1) {
            INFO_LOGGING("a client is die");
            return -1;
        }
        client_file_size = *(off_t *)train.data;
        //计算md5值，判断是断点续下载还是重名普通文件
        char server_md5_val[33] = {0};
        get_md5(server_path, server_md5_val, client_file_size);
        int is_not_pause_download = memcmp(client_md5_val, server_md5_val, sizeof(client_md5_val));
        if (is_not_pause_download != 0) {//是普通重名
                                         //发送错误处理的小火车
            char errbuf[] = "gets: 已经存在同名文件，或者下载文件是一个上传未完成文件";
            padding_train_by_buf(&train, errbuf, GETS_FAILED,sizeof(errbuf));
            int ret  = send_train(netfd, &train);
            WARNING_LOGGING(ret, -1, "client is die in getting\n");

            WARNING_LOGGING(1, 1, "gets: 已经存在同名文件，或者下载文件是一个上传未完成文件\n");
            close(src_file_fd);
            return 0;

        }else {//是断点继续下载
               //更新流的位置和大小
            size -= client_file_size;
            lseek(src_file_fd, client_file_size, SEEK_SET);
            offset = client_file_size;
        }
    }
    //发送文件大小
    padding_train_by_buf(&train, &size, COMMAND_GETS,sizeof(size));
    ret = send_train(netfd, &train);
    WARNING_LOGGING(ret, -1, "client is die in getting\n");
    //判断是大文件传输还是小文件传输
    if (size > BIG_FILE) {//大文件
        send_big_file(netfd,src_file_fd, size, offset);
    }else {//小文件
        send_little_file(netfd, src_file_fd, size);
    }

    return 0;
}



//上传服务端,server_path是不带文件名的路径
int my_puts(int netfd, MYSQL * mysql, const char * filename,const char * server_path, const char * client_path, const char * user_name, const char * file_table_name) {
    //将客户端路径发回客户端
    //这是发给客户端的第一个包
    Train_t train;
    padding_train_by_buf(&train, (void*)client_path, COMMAND_PUTS,strlen(client_path) + 1);
    int ret = send_train(netfd, &train);
    WARNING_LOGGING(ret, -1, "client is die in getting\n");
    //接收客户端的小火车
    //这是接收客户端的第一个包
    int sret = recv_train(netfd, &train);
    if (sret == -1) {
        INFO_LOGGING("a client is die");
        return -1;
    }
    //通过type判断客户端是否存在这个文件
    if (train.data_type == PUTS_FAILED) {//客户端不存在该文件
        INFO_LOGGING("client do not have file");
        return 0;
    }
    //客户端存在该文件

    //接收客户端文件大小
    //这是接收客户端的第2个包
    sret = recv_train(netfd, &train);
    if (sret == -1) {
        INFO_LOGGING("a client is die");
        return -1;
    }
    //保存客户端文件大小(需要上传的源文件)
    off_t client_file_size = *(off_t *)train.data;
    //记录一个文件偏移量
    off_t offset = 0;
    //接收客户端文件md5
    //这是接收客户端的第3个包
    sret = recv_train(netfd, &train);
    if (sret == -1) {
        INFO_LOGGING("a client is die");
        return -1;
    }
    //保存客户端文件的md5
    char client_md5_val[33] = {0};
    memcpy(client_md5_val, train.data, sizeof(client_md5_val));
    //拼接出file的,带file的绝对路径
    char server_file_path[1024] = {0};
    sprintf(server_file_path, "%s/%s", server_path, filename);
    //用select_field函数判断是否存在同名文件
    char serch_md5_fileld[100] = {"md5"};//查询字段
    char condition[100] = {0};//查询条件
    sprintf(condition, "path = '%s' and username = '%s'", server_file_path, user_name);
    int comp = memcmp(client_md5_val, serch_md5_fileld, sizeof(client_md5_val));
    int is_exist = select_field(mysql, serch_md5_fileld, file_table_name, condition);
    if (is_exist != 0 && comp != 0) {//重名并且不是断点续传文件文件不可上传
        char errbuf[] = {"puts:重名并且不是断点续传文件文件不可上传"};
        //向客户端发送错误终止包
        //这是向客户端发送的第2个包
        padding_train_by_buf(&train, errbuf,PUTS_FAILED,  sizeof(errbuf));
        send_train(netfd, &train);
        INFO_LOGGING("puts:重名并且不是断点续传文件文件不可上传");
        return 0;

    }else{//没有重名的文件,需要添加文件表项
          //用select_field函数查找父id
        char serch_parent_id[100] = {"file_id"};
        memset(condition, 0, sizeof(condition));
        sprintf(condition, "path = '%s' and username = '%s'", server_path, user_name);
        select_field(mysql, serch_parent_id, file_table_name, condition);
        //用insert_file_row函数插入文件表项
        char insert_file_row[100] = {0};//插入行
        memset(condition, 0, sizeof(condition));
        sprintf(insert_file_row, "(filename,username,parent_id,path,type,md5,is_complete)"
                "values('%s','%s',%s,'%s','f','%s',0)",filename,user_name,serch_parent_id,server_file_path,client_md5_val);

        insert_into__row(mysql, file_table_name, insert_file_row);
    }
    //用文件偏移量判断是不是秒传

    //以追加模式打开文件
    int file_fd = open(serch_md5_fileld, O_RDWR | O_APPEND);
    //用fstat查看文件大小
    struct stat state;
    fstat(file_fd, &state);
    off_t server_file_size = state.st_size;
    //更新偏移量
    offset += server_file_size;
    //判断秒传
    //1.可能是上次没传完，但是有其他人上传了这个文件，
    //2.可能是上次已经传过了一个同名的文件
    //3.还有可能是自己没有传过别人传过
    if (offset == client_file_size) {
        //将is_complete改成1
        char update_result[100] = {"is_complete = 1"};
        sprintf(condition, "path = '%s' and username = '%s'", server_file_path, user_name);
        memset(condition, 0, sizeof(condition));
        update_fileld(mysql, file_table_name, update_result, condition);
        //发送成功秒传的信息
        //这是发送客户端的第2个包
        char success_buf[] = {"puts : success second"};
        padding_train_by_buf(&train, success_buf,PUTS_SECOND, sizeof(success_buf));
        send_train(netfd, &train);
        return 0;
    }

    //不是秒传
    //向客户端发送偏移量
    //这是发送客户端的第2个包
    padding_train_by_buf(&train, &offset, COMMAND_PUTS, sizeof(offset));
    send_train(netfd, &train);
    //判断是大文件接收还是小文件接收
    off_t recv_len = client_file_size - offset;
    if (recv_len > BIG_FILE) {
        //大文件接收
        int ret = recv_and_gets_big_file(file_fd, netfd, recv_len, offset); 
        if (ret == -1) {
            INFO_LOGGING("client is die in puts");
            close(file_fd);
            return -1;
        }
    }else {
        //小文件接收
        int ret = recv_and_gets_little_file(file_fd, netfd, recv_len);
        if (ret == -1) {
            INFO_LOGGING("client is die in puts");
            close(file_fd);
            return -1;
        }
    }
    return 0;
}

